// (C) 2021 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

module port80_if 
 (
    // Port 80
    input wire        p80_clk,
    input wire        p80_reset,

    input wire [15:0] io_address,
    input wire        io_write,
    input wire [7:0]  io_writedata,

    output reg [31:0] p80_rd_data,
    input wire        p80_rd_data_re,
    output reg        p80_rd_isr,
    input wire        p80_rd_isr_re,
    output reg [7:0]  p80_rd_bytes_avail,

    // MSI-X Request to espi_avmm module 
    output reg        espi_port80_msix_req,
    input wire        espi_port80_msix_ack
);
    // read state machine states 
    typedef enum bit [2:0] {P80_RD_IDLE,
                            P80_RD_GEN_MSIX,
                            P80_RD_WAIT_ISR,
                            P80_RD_PREFETCH,
                            P80_RD_PREFETCH_WAIT,
                            P80_RD_CHECK_PREFETCH,
                            P80_RD_WAIT_RD_FIFO,
                            P80_RD_CHECK_DONE} p80_rd_states;

    // internal logic signals
    // read state
    p80_rd_states p80_rd_state         = P80_RD_IDLE;
    reg [2:0]  p80_prefetch_cnt        = 3'h0;
    reg [7:0]  p80_rd_bytes_read       = 8'h0;
    reg        p80_rd_bytes_read_rst   = 1'b0;
    reg        p80_rd_prefetch_cnt_rst = 1'b0;
    reg        p80_rd_prefetch_cnt_inc = 1'b0;

    wire [7:0] p80_rd_data_q;
    wire [7:0] p80_rd_fifo_words; 
    wire       p80_rd_fifo_empty; 
    wire       p80_rd_fifo_re;
    wire       p80_rd_bytes_read_inc;
    //
    // write
    wire       p80_wr_fifo_we;

    // Interrupt Status Register
    //  Set the ISR in GEN_MSIX state
    //  clear when ISR is read
    always @ (posedge p80_clk)
      if (p80_reset)
        p80_rd_isr <= 1'h0;
      else if (p80_rd_state == P80_RD_GEN_MSIX) 
        p80_rd_isr <= 1'h1;
      else if (p80_rd_isr_re) 
        p80_rd_isr <= 1'h0;
    
    // Transmit a request to send an MSI-X to BMC
    always @ (posedge p80_clk)
      if (p80_reset)
        espi_port80_msix_req <= 1'h0;
      else if (p80_rd_state == P80_RD_GEN_MSIX) 
        espi_port80_msix_req <= 1'h1;
      else if (espi_port80_msix_ack) 
        espi_port80_msix_req <= 1'h0;

    // Port 80 Read FIFO Control State machine
    always @ (posedge p80_clk)
      if (p80_reset)
        begin
          // default outputs
          p80_rd_state            <= P80_RD_IDLE;
          p80_rd_bytes_avail      <= 8'b0;
          p80_rd_prefetch_cnt_inc <= 1'b0;
          p80_rd_prefetch_cnt_rst <= 1'b0;
          p80_rd_bytes_read_rst   <= 1'b0;
        end
      else
        case(p80_rd_state)

         P80_RD_IDLE:
          begin
              p80_rd_prefetch_cnt_inc  <= 1'b0;
              p80_rd_prefetch_cnt_rst  <= 1'b1;
              p80_rd_bytes_read_rst    <= 1'b1;

              if (p80_rd_fifo_empty == 1'b0)
                p80_rd_state <= P80_RD_GEN_MSIX;
          end

         P80_RD_GEN_MSIX:
         // Generate an MSI-X request, exit on acknowledge
          begin
              if (espi_port80_msix_ack == 1'b1)
                p80_rd_state <= P80_RD_WAIT_ISR;
          end

         P80_RD_WAIT_ISR:
          begin
              p80_rd_bytes_read_rst <= 1'b0;
              p80_rd_bytes_avail    <= p80_rd_fifo_words;
              if (p80_rd_isr_re == 1'b1)
                p80_rd_state      <= P80_RD_PREFETCH;
          end

         P80_RD_PREFETCH:
          begin
              p80_rd_prefetch_cnt_rst <= 1'b0;
              p80_rd_prefetch_cnt_inc <= 1'b1;
              p80_rd_state            <= P80_RD_PREFETCH_WAIT;
          end

         P80_RD_PREFETCH_WAIT:
          begin
              p80_rd_prefetch_cnt_inc <= 1'b0;
              p80_rd_state            <= P80_RD_CHECK_PREFETCH;
          end

         P80_RD_CHECK_PREFETCH:
          begin
              if (p80_rd_bytes_read >= p80_rd_bytes_avail)
                p80_rd_state      <= P80_RD_CHECK_DONE;
              else if (p80_prefetch_cnt == 3'h4)
                p80_rd_state      <= P80_RD_WAIT_RD_FIFO;
              else
                p80_rd_state      <= P80_RD_PREFETCH;
          end

         P80_RD_WAIT_RD_FIFO:
          begin
              p80_rd_prefetch_cnt_rst <= 1'b1;
              if (p80_rd_data_re == 1'b1)
                p80_rd_state      <= P80_RD_PREFETCH;
          end

         P80_RD_CHECK_DONE:
          begin
              p80_rd_prefetch_cnt_rst <= 1'b1;

              if (p80_rd_bytes_read >= p80_rd_bytes_avail)
                p80_rd_state      <= P80_RD_IDLE;
              else
                p80_rd_state      <= P80_RD_PREFETCH;
          end

         default:
          begin
            p80_rd_state <= P80_RD_IDLE;
          end

        endcase // case(p80_rd_state)

    assign p80_rd_fifo_re        = p80_rd_prefetch_cnt_inc;
    assign p80_rd_bytes_read_inc = p80_rd_prefetch_cnt_inc;

    always @ (posedge p80_clk)
      if (p80_reset)
        p80_prefetch_cnt <= 3'h0;
      else if (p80_rd_prefetch_cnt_rst) 
        p80_prefetch_cnt <= 3'h0;
      else if (p80_rd_prefetch_cnt_inc) 
        p80_prefetch_cnt <= p80_prefetch_cnt + 1'h1;

    always @ (posedge p80_clk)
      if (p80_reset)
        p80_rd_bytes_read <= 8'h0;
      else if (p80_rd_bytes_read_rst) 
        p80_rd_bytes_read <= 8'h0;
      else if (p80_rd_bytes_read_inc) 
        p80_rd_bytes_read <= p80_rd_bytes_read + 1'h1;


    always @ (posedge p80_clk)
      if (p80_reset)
        p80_rd_data <= 32'h0;
      else if (p80_rd_fifo_re)
        case(p80_prefetch_cnt)
            3'h0: begin
                    p80_rd_data[07:00] <= p80_rd_data_q;
                    p80_rd_data[15:08] <= 8'h0;
                    p80_rd_data[23:16] <= 8'h0;
                    p80_rd_data[31:24] <= 8'h0;
                  end
             3'h1: p80_rd_data[15:08] <= p80_rd_data_q;
             3'h2: p80_rd_data[23:16] <= p80_rd_data_q;
             3'h3: p80_rd_data[31:24] <= p80_rd_data_q;
        endcase // case(p80_prefetch_cnt)

    port80_fifo port80_fifo (
    .clock     ( p80_clk           ),
    .data      ( io_writedata  ),
    .rdreq     ( p80_rd_fifo_re    ),
    .wrreq     ( p80_wr_fifo_we    ),
    .empty     ( p80_rd_fifo_empty ),
    .full      (                   ),
    .q         ( p80_rd_data_q     ),
    .usedw     ( p80_rd_fifo_words )
    );

    assign p80_wr_fifo_we = io_write & (io_address[15:4] == 12'h008);

endmodule
